package com.laomou.logdog.controller;


import com.laomou.logdog.AdbCommand;
import com.laomou.logdog.ColorTableCellFactory;
import com.laomou.logdog.loginfo.LogInfo;
import com.laomou.logdog.loginfo.LogInfoParser;
import com.laomou.logdog.loginfo.LoginfoExceptionParser;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.MouseButton;
import javafx.stage.FileChooser;

import java.io.*;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Predicate;

public class MainController implements Initializable {
    public static final int STATUS_CHANGE = 1;
    public static final int STATUS_PARSING = 2;
    public static final int STATUS_READY = 4;

    public static final int LOG_LV_VERBOSE = 1;
    public static final int LOG_LV_DEBUG = LOG_LV_VERBOSE << 1;
    public static final int LOG_LV_INFO = LOG_LV_DEBUG << 1;
    public static final int LOG_LV_WARN = LOG_LV_INFO << 1;
    public static final int LOG_LV_ERROR = LOG_LV_WARN << 1;
    public static final int LOG_LV_FATAL = LOG_LV_ERROR << 1;
    public static final int LOG_LV_ALL = LOG_LV_VERBOSE | LOG_LV_DEBUG | LOG_LV_INFO | LOG_LV_WARN | LOG_LV_ERROR | LOG_LV_FATAL;

    public static final int TABLE_COLUMN_LINE = 1;
    public static final int TABLE_COLUMN_DATE = TABLE_COLUMN_LINE << 1;
    public static final int TABLE_COLUMN_TIME = TABLE_COLUMN_DATE << 1;
    public static final int TABLE_COLUMN_LEVEL = TABLE_COLUMN_TIME << 1;
    public static final int TABLE_COLUMN_PID = TABLE_COLUMN_LEVEL << 1;
    public static final int TABLE_COLUMN_TID = TABLE_COLUMN_PID << 1;
    public static final int TABLE_COLUMN_TAG = TABLE_COLUMN_TID << 1;
    public static final int TABLE_COLUMN_MESSAGE = TABLE_COLUMN_TAG << 1;
    public static final int TABLE_COLUMN_ALL = TABLE_COLUMN_LINE | TABLE_COLUMN_DATE | TABLE_COLUMN_TIME | TABLE_COLUMN_LEVEL | TABLE_COLUMN_PID | TABLE_COLUMN_TID | TABLE_COLUMN_TAG | TABLE_COLUMN_MESSAGE;

    public static final int EXCEPTION_RUNE = 1;
    public static final int EXCEPTION_ANR = EXCEPTION_RUNE << 1;
    public static final int EXCEPTION_ALL = EXCEPTION_RUNE | EXCEPTION_ANR;

    private final Object FILTER_LOCK = new Object();
    private final Object FILE_LOCK = new Object();
    @FXML
    TextField m_tfStatus;
    @FXML
    TextField m_tfMeminfo;
    @FXML
    private OptionController optionController;
    @FXML
    private TableView<LogInfo> m_tbLogTable;

    private boolean m_bUserFilter;
    private boolean m_bExceptionFilter;
    private boolean m_bPauseADB;

    private ObservableList<LogInfo> logInfoAll;
    private ObservableList<LogInfo> logInfoFiltered;
    private ObservableList<LogInfo> loginfoException;
    private ObservableList<TableColumn<LogInfo, ?>> tableColumns;

    private LogInfoParser logInfoParser;
    private volatile int m_nChangedFilter;
    private int m_nFilterLogLV;
    private int m_nFilterColumn;
    private int m_nFilterException;
    private String m_strLogFileName;

    private Thread m_thFilterParse;
    private Process m_Process;
    private Thread m_thProcess;
    private Thread m_thWatchFile;
    private AdbCommand adbCommand;
    private Timer meminfoTimer;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        optionController.setMainconreoller(this);
        initValue();
        register();
        welcome();
        startFilterParse();
        optionController.loadFilter();
    }

    private void initValue() {
        m_bUserFilter = false;
        m_bPauseADB = false;
        m_nFilterLogLV = LOG_LV_ALL;
        m_nFilterColumn = TABLE_COLUMN_ALL;
        m_nFilterException = 0;
        logInfoParser = new LogInfoParser();
        adbCommand = new AdbCommand();
        logInfoAll = FXCollections.observableArrayList();
        logInfoFiltered = FXCollections.observableArrayList();
        tableColumns = FXCollections.observableArrayList();
        loginfoException = FXCollections.observableArrayList();
        tableColumns.addAll(m_tbLogTable.getColumns());
        m_tbLogTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
    }

    private void register() {
        m_tbLogTable.setOnMouseClicked(event -> {
            if (event.getButton() == MouseButton.SECONDARY) {
                StringBuilder stringBuilder = new StringBuilder();
                m_tbLogTable.getSelectionModel().getSelectedItems().forEach(logInfo -> {
                    stringBuilder.append(logInfo.string());
                    stringBuilder.append("\r\n");
                });
                ClipboardContent clipboardContent = new ClipboardContent();
                clipboardContent.putString(stringBuilder.toString());
                if (clipboardContent.hasString()) {
                    Clipboard.getSystemClipboard().setContent(clipboardContent);
                }
            } else if (event.getButton() == MouseButton.PRIMARY) {
                ObservableList<LogInfo> list = m_tbLogTable.getSelectionModel().getSelectedItems();
                if (list.size() > 0) {
                    if (list.get(0).getLine().isEmpty()) {
                        return;
                    }
                    int line = Integer.valueOf(list.get(0).getLine());
                    optionController.setGotoLineText(line + "");
                }
            }
        });
        meminfoTimer = new Timer();
        TimerTask task = new TimerTask() {
            public void run() {
                updateMeminfo();
            }
        };
        try {
            meminfoTimer.schedule(task, 0, 1000);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void welcome() {
        logInfoAll.add(addLogInfo("LogDog for Android log filter"));
        logInfoAll.add(addLogInfo("选中列表里的内容,右键单击即可复制"));
        logInfoAll.add(addLogInfo("支持Word关键字过滤"));
        logInfoAll.add(addLogInfo("支持Tag关键字过滤"));
        logInfoAll.add(addLogInfo("支持Log等级过滤"));
        logInfoAll.add(addLogInfo("支持列表项过滤"));
        logInfoAll.add(addLogInfo("支持Exception过滤"));
        m_tbLogTable.setItems(logInfoAll);
    }

    private LogInfo addLogInfo(String msg) {
        LogInfo logInfo = new LogInfo();
        logInfo.setMessage(msg);
        return logInfo;
    }

    @FXML
    public void onFileOpen() {
        FileChooser fileChooser = new FileChooser();
        List<File> files = fileChooser.showOpenMultipleDialog(null);
        preParseFiles(files);
    }

    @FXML
    public void onExit() {
        onClose();
        optionController.saveFilter();
        System.exit(0);
    }

    private String makeFilename() {
        Date now = new Date();
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd_HHmmss");
        return "LogDog_" + format.format(now) + ".txt";
    }

    public void onClose() {
        if (m_Process != null) {
            m_Process.destroy();
        }
        if (m_thFilterParse != null) {
            m_thFilterParse.interrupt();
        }
        if (m_thProcess != null) {
            m_thProcess.interrupt();
        }
        if (m_thWatchFile != null) {
            m_thWatchFile.interrupt();
        }
        if (meminfoTimer != null) {
            meminfoTimer.cancel();
        }
    }

    private void updateMeminfo() {
        Runtime runtime = Runtime.getRuntime();
        long used = (runtime.totalMemory() - runtime.freeMemory()) / 1024;
        long total = runtime.totalMemory() / 1024;
        Platform.runLater(() -> m_tfMeminfo.setText("内存使用: " + Math.round((float) used / total * 100) + "%"));
    }

    private void setStatus(String strText) {
        Platform.runLater(() -> m_tfStatus.setText(strText));
    }

    private void startFilterParse() {
        m_thFilterParse = new Thread(() -> {
            try {
                while (true) {
                    synchronized (FILTER_LOCK) {
                        m_nChangedFilter = STATUS_READY;
                        FILTER_LOCK.wait();

                        m_nChangedFilter = STATUS_PARSING;
                        logInfoFiltered.clear();
                        loginfoException.clear();
                        m_tbLogTable.getSelectionModel().clearSelection();

                        if (!m_bUserFilter && !m_bExceptionFilter) {
                            m_tbLogTable.setItems(logInfoAll);
                            m_nChangedFilter = STATUS_READY;
                            continue;
                        }

                        m_tbLogTable.setItems(logInfoFiltered);
                        setStatus("Filtering");

                        if (m_bExceptionFilter) {
                            FilteredList<LogInfo> filteredData = new FilteredList<>(logInfoAll);
                            filteredData.setPredicate(new Predicate<LogInfo>() {
                                private LoginfoExceptionParser loginfoExceptionParser = new LoginfoExceptionParser();

                                @Override
                                public boolean test(LogInfo logInfo) {
                                    loginfoExceptionParser.setExceptionType(m_nFilterException);
                                    return loginfoExceptionParser.processLogE(logInfo);
                                }
                            });
                            loginfoException.addAll(filteredData);
                        }

                        FilteredList<LogInfo> filteredData = new FilteredList<>(m_bExceptionFilter ? loginfoException : logInfoAll);
                        filteredData.setPredicate(logInfo -> (m_bExceptionFilter || checkLogLVFilter(logInfo))
                                && checkPidFilter(logInfo)
                                && checkTidFilter(logInfo)
                                && checkFindTagFilter(logInfo)
                                && checkRemoveTagFilter(logInfo)
                                && checkFindWordFilter(logInfo)
                                && checkRemoveWordFilter(logInfo));
                        logInfoFiltered.addAll(filteredData);

                        if (m_nChangedFilter == STATUS_PARSING) {
                            m_tbLogTable.refresh();
                            m_nChangedFilter = STATUS_READY;
                            setStatus("Filter Complete");
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        m_thFilterParse.start();
    }

    private void startFileParse() {
        m_thWatchFile = new Thread(() -> {
            FileInputStream fstream = null;
            DataInputStream in = null;
            BufferedReader br = null;

            try {
                fstream = new FileInputStream(m_strLogFileName);
                in = new DataInputStream(fstream);
                br = new BufferedReader(new InputStreamReader(in, "UTF-8"));

                String strLine;
                int nAddCount = 0;
                logInfoAll.clear();

                while (true) {
                    Thread.sleep(50);
                    if (m_nChangedFilter == STATUS_CHANGE || m_nChangedFilter == STATUS_PARSING) {
                        continue;
                    }
                    if (m_bPauseADB) {
                        continue;
                    }
                    synchronized (FILE_LOCK) {
                        int nLine = logInfoAll.size() + 1;
                        while (!m_bPauseADB && (strLine = br.readLine()) != null) {
                            if (strLine != null && !"".equals(strLine.trim())) {
                                LogInfo logInfo = logInfoParser.processLogLine(strLine);
                                logInfo.setLine("" + nLine++);
                                addLogInfo(logInfo);
                                nAddCount++;
                            }
                        }
                    }
                    if (nAddCount == 0) {
                        continue;
                    }
                    synchronized (FILTER_LOCK) {
                        if (!m_bUserFilter) {
                            m_tbLogTable.setItems(logInfoAll);
                        } else {
                            m_tbLogTable.setItems(logInfoFiltered);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (br != null) br.close();
                if (in != null) in.close();
                if (fstream != null) fstream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        m_thWatchFile.start();
    }

    public void pauseProcess() {
        if (optionController.enablePause()) {
            m_bPauseADB = true;
            optionController.setPauseText("Resume", true);
        } else {
            m_bPauseADB = false;
            optionController.setPauseText("Pause", false);
        }
    }

    public void startProcess() {
        clearData();
        m_tbLogTable.getSelectionModel().clearSelection();
        m_thProcess = new Thread(() -> {
            try {
                String s;
                m_Process = null;
                m_strLogFileName = makeFilename();
                String strCommand = adbCommand.adbLogCat(optionController.getDeviceName(), optionController.getDeviceCmd());
                m_Process = Runtime.getRuntime().exec(strCommand);
                BufferedReader stdOut = new BufferedReader(new InputStreamReader(m_Process.getInputStream(), "UTF-8"));
                Writer fileOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(m_strLogFileName), "UTF-8"));
                startFileParse();
                while ((s = stdOut.readLine()) != null) {
                    if (s != null && !"".equals(s.trim())) {
                        synchronized (FILE_LOCK) {
                            fileOut.write(s);
                            fileOut.write("\r\n");
                            fileOut.flush();
                        }
                    }
                }
                fileOut.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        m_thProcess.start();
    }

    public void stopProcess() {
        optionController.setProcessBtn(false);
        if (m_Process != null) {
            m_Process.destroy();
        }
        if (m_thProcess != null) {
            m_thProcess.interrupt();
        }
        if (m_thWatchFile != null) {
            m_thWatchFile.interrupt();
        }
        m_Process = null;
        m_thProcess = null;
        m_thWatchFile = null;
        m_bPauseADB = false;

    }

    private void preParseFiles(List<File> files) {
        if (files == null) {
            return;
        }
        clearData();
        System.gc();
        parseFiles(files);
    }

    private void parseFiles(final List<File> files) {
        new Thread(() -> {
            FileInputStream fstream = null;
            DataInputStream in = null;
            BufferedReader br = null;
            int nIndex = 1;

            for (File file : files) {
                try {
                    fstream = new FileInputStream(file);
                    in = new DataInputStream(fstream);
                    br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
                    String strLine;
                    setStatus("Parsing " + file.getName());
                    // clearData();
                    m_tbLogTable.getSelectionModel().clearSelection();
                    while ((strLine = br.readLine()) != null) {
                        if (strLine != null && !"".equals(strLine.trim())) {
                            LogInfo logInfo = logInfoParser.processLogLine(strLine);
                            logInfo.setLine("" + nIndex++);
                            addLogInfo(logInfo);
                        }
                    }
                } catch (Exception ioe) {
                    ioe.printStackTrace();
                }
                try {
                    if (br != null) br.close();
                    if (in != null) in.close();
                    if (fstream != null) fstream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            // runFilter();
            // setStatus("Parse Complete");
        }).start();
    }

    public void clearData() {
        logInfoAll.clear();
        logInfoFiltered.clear();
        loginfoException.clear();
    }

    private void addLogInfo(LogInfo logInfo) {
        synchronized (FILTER_LOCK) {
            logInfoAll.add(logInfo);
            if (m_bUserFilter) {
                if (checkLogLVFilter(logInfo)
                        && checkPidFilter(logInfo)
                        && checkTidFilter(logInfo)
                        && checkFindTagFilter(logInfo)
                        && checkRemoveTagFilter(logInfo)
                        && checkFindWordFilter(logInfo)
                        && checkRemoveWordFilter(logInfo)) {
                    logInfoFiltered.add(logInfo);
                }
            }
        }
    }

    public void setChangedFilter(int changedFilter) {
        this.m_nChangedFilter = changedFilter;
    }

    public void runFilter() {
        checkUseFilter();
        while (m_nChangedFilter == STATUS_PARSING) {
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        synchronized (FILTER_LOCK) {
            FILTER_LOCK.notify();
        }
    }

    public void setLogLV(int nLogLV, boolean bChecked) {
        if (bChecked) {
            m_nFilterLogLV |= nLogLV;
        } else {
            m_nFilterLogLV &= ~nLogLV;
        }
        m_nChangedFilter = STATUS_CHANGE;
        runFilter();
    }

    private boolean checkUseFilter() {
        if (checkLogLVFilter(new LogInfo())
                && (optionController.getPid().length() == 0 || !optionController.enablePid())
                && (optionController.getTid().length() == 0 || !optionController.enableTid())
                && (optionController.getFindTag().length() == 0 || !optionController.enableTagShow())
                && (optionController.getRemoveTag().length() == 0 || !optionController.enableTagRemove())
                && (optionController.getFindWord().length() == 0 || !optionController.enableFindWord())
                && (optionController.getRemoveWord().length() == 0 || !optionController.enableRemoveWord())
                ) {
            m_bUserFilter = false;
        } else {
            m_bUserFilter = true;
        }
        return m_bUserFilter;
    }

    private boolean checkPidFilter(LogInfo logInfo) {
        if (optionController.getPid().length() <= 0) return true;
        StringTokenizer stk = new StringTokenizer(optionController.getPid(), "|", false);
        while (stk.hasMoreElements()) {
            if (logInfo.getPid().contains(stk.nextToken())) {
                return true;
            }
        }
        return false;
    }

    private boolean checkTidFilter(LogInfo logInfo) {
        if (optionController.getTid().length() <= 0) return true;
        StringTokenizer stk = new StringTokenizer(optionController.getTid(), "|", false);
        while (stk.hasMoreElements()) {
            if (logInfo.getTid().contains(stk.nextToken())) {
                return true;
            }
        }
        return false;
    }

    private boolean checkFindWordFilter(LogInfo logInfo) {
        if (optionController.getFindWord().length() <= 0) return true;
        StringTokenizer stk = new StringTokenizer(optionController.getFindWord(), "|", false);
        while (stk.hasMoreElements()) {
            if (logInfo.getMessage().toLowerCase().contains(stk.nextToken().toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    private boolean checkRemoveWordFilter(LogInfo logInfo) {
        if (optionController.getRemoveWord().length() <= 0) return true;
        StringTokenizer stk = new StringTokenizer(optionController.getRemoveWord(), "|", false);
        while (stk.hasMoreElements()) {
            if (logInfo.getMessage().toLowerCase().contains(stk.nextToken().toLowerCase())) {
                return false;
            }
        }
        return true;
    }

    private boolean checkFindTagFilter(LogInfo logInfo) {
        if (optionController.getFindTag().length() <= 0) return true;
        StringTokenizer stk = new StringTokenizer(optionController.getFindTag(), "|", false);
        while (stk.hasMoreElements()) {
            if (logInfo.getTag().toLowerCase().contains(stk.nextToken().toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    private boolean checkRemoveTagFilter(LogInfo logInfo) {
        if (optionController.getRemoveTag().length() <= 0) return true;
        StringTokenizer stk = new StringTokenizer(optionController.getRemoveTag(), "|", false);
        while (stk.hasMoreElements()) {
            if (logInfo.getTag().toLowerCase().contains(stk.nextToken().toLowerCase())) {
                return false;
            }
        }
        return true;
    }

    private boolean checkLogLVFilter(LogInfo logInfo) {
        if (m_nFilterLogLV == LOG_LV_ALL) {
            return true;
        }
        if ((m_nFilterLogLV & LOG_LV_VERBOSE) != 0 && (logInfo.getLogLv().equals("V"))) {
            return true;
        }
        if ((m_nFilterLogLV & LOG_LV_DEBUG) != 0 && (logInfo.getLogLv().equals("D"))) {
            return true;
        }
        if ((m_nFilterLogLV & LOG_LV_INFO) != 0 && (logInfo.getLogLv().equals("I"))) {
            return true;
        }
        if ((m_nFilterLogLV & LOG_LV_WARN) != 0 && (logInfo.getLogLv().equals("W"))) {
            return true;
        }
        if ((m_nFilterLogLV & LOG_LV_ERROR) != 0 && (logInfo.getLogLv().equals("E"))) {
            return true;
        }
        if ((m_nFilterLogLV & LOG_LV_FATAL) != 0 && (logInfo.getLogLv().equals("F"))) {
            return true;
        }
        return false;
    }

    public void gotoLine(int line) {
        if (m_bUserFilter) {
            LogInfo info = logInfoAll.get(line - 1);
            int index = logInfoFiltered.indexOf(info);
            if (index != -1) {
                m_tbLogTable.scrollTo(index);
            }
        } else if (m_bExceptionFilter) {
            LogInfo info = logInfoAll.get(line - 1);
            int index = loginfoException.indexOf(info);
            if (index != -1) {
                m_tbLogTable.scrollTo(index);
            }
        } else {
            m_tbLogTable.scrollTo(line - 1);
        }
    }

    public void setFontSize(int size) {
        m_tbLogTable.getColumns().forEach(logInfoTableColumn -> {
            ColorTableCellFactory cellFactory = (ColorTableCellFactory) logInfoTableColumn.getCellFactory();
            cellFactory.setFontSize(size);
        });
        updateTable();
    }

    public void updateTable() {
        m_tbLogTable.refresh();
    }

    public void setTableColumn(int nTableColumn, boolean bChecked) {
        if (bChecked) {
            m_nFilterColumn |= nTableColumn;
        } else {
            m_nFilterColumn &= ~nTableColumn;
        }
        updateTableColumn();
    }

    private boolean checkTableColumnFilter(TableColumn<LogInfo, ?> logInfoTableColumn) {
        if (m_nFilterColumn == TABLE_COLUMN_ALL) {
            return true;
        }
        PropertyValueFactory property = (PropertyValueFactory) logInfoTableColumn.getCellValueFactory();
        if ((m_nFilterColumn & TABLE_COLUMN_LINE) != 0 && property.getProperty().equals("line")) {
            return true;
        }
        if ((m_nFilterColumn & TABLE_COLUMN_DATE) != 0 && property.getProperty().equals("date")) {
            return true;
        }
        if ((m_nFilterColumn & TABLE_COLUMN_TIME) != 0 && property.getProperty().equals("time")) {
            return true;
        }
        if ((m_nFilterColumn & TABLE_COLUMN_LEVEL) != 0 && property.getProperty().equals("logLv")) {
            return true;
        }
        if ((m_nFilterColumn & TABLE_COLUMN_PID) != 0 && property.getProperty().equals("pid")) {
            return true;
        }
        if ((m_nFilterColumn & TABLE_COLUMN_TID) != 0 && property.getProperty().equals("tid")) {
            return true;
        }
        if ((m_nFilterColumn & TABLE_COLUMN_TAG) != 0 && property.getProperty().equals("tag")) {
            return true;
        }
        if ((m_nFilterColumn & TABLE_COLUMN_MESSAGE) != 0 && property.getProperty().equals("message")) {
            return true;
        }
        return false;
    }

    private void updateTableColumn() {
        FilteredList<TableColumn<LogInfo, ?>> filteredList = new FilteredList<>(tableColumns);
        filteredList.setPredicate(logInfoTableColumn -> checkTableColumnFilter(logInfoTableColumn));
        m_tbLogTable.getColumns().setAll(filteredList);
    }

    public void updateDeviceList() {
        ObservableList<String> list = FXCollections.observableArrayList();
        try {
            String s;
            String strCommand = adbCommand.adbDevices();
            Process oProcess = Runtime.getRuntime().exec(strCommand);

            BufferedReader stdOut = new BufferedReader(new InputStreamReader(oProcess.getInputStream()));

            while ((s = stdOut.readLine()) != null) {
                if (!s.contains("List of devices attached")
                        && !s.contains("daemon not running")
                        && !s.contains("daemon started successfully")
                        && !s.contains("adb server is out of date")) {
                    s = s.replace("\t", "");
                    s = s.replace("device", "");
                    if (s != null && !"".equals(s.trim())) {
                        list.add(s);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        optionController.setDeviceList(list);
    }

    public boolean enableExceptionFilter() {
        return m_bExceptionFilter;
    }

    public void setExceptionFilter(int nException, boolean bChecked) {
        if (bChecked) {
            m_nFilterException |= nException;
        } else {
            m_nFilterException &= ~nException;
        }
        if (m_nFilterException == 0) {
            m_bExceptionFilter = false;
        } else {
            m_bExceptionFilter = true;
        }
        m_nChangedFilter = STATUS_CHANGE;
        runFilter();
    }
}
